// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package asn1

import (
	"reflect"
	"strconv"
	"strings"
)

// ASN。1对象前面有元数据：
// 标记：对象的类型
// 表示此对象是否为复合对象的标志
// 类类型：标记的名称空间
// 对象的长度，字节

// 以下是一些标准标记和类

// ASN。1标记表示以下对象的类型。
const (
	TagBoolean         = 1
	TagInteger         = 2
	TagBitString       = 3
	TagOctetString     = 4
	TagNull            = 5
	TagOID             = 6
	TagEnum            = 10
	TagUTF8String      = 12
	TagSequence        = 16
	TagSet             = 17
	TagNumericString   = 18
	TagPrintableString = 19
	TagT61String       = 20
	TagIA5String       = 22
	TagUTCTime         = 23
	TagGeneralizedTime = 24
	TagGeneralString   = 27
	TagBMPString       = 30
)

// ASN。1类类型表示标记的名称空间。
const (
	ClassUniversal       = 0
	ClassApplication     = 1
	ClassContextSpecific = 2
	ClassPrivate         = 3
)

type tagAndLength struct {
	class, tag, length int
	isCompound         bool
}

// ASN。1有隐式和显式标记，可以翻译为“代替
// of”和“除此之外”。未指定时，每个基元类型在通用类中都有一个
// 默认标记。
// 
// 例如：默认情况下，位字符串被标记为[UNIVERSAL 3]（尽管ASN.1 
// 实际上没有通用关键字）。然而，通过说[IMPLICIT 
// CONTEXT-SPECIFIC 42]，这意味着标签被另一个标签替换。
// 
// 另一方面，如果它说[EXPLICIT CONTEXT-SPECIFIC 10]，那么一个
// /额外的/tag将包装默认标记。此显式标记将设置
// 复合标志。
// 
// （用于消除可选元素的歧义。）
// 
// 您可以将显式和隐式标记分层到任意深度，但是我们这里不支持这种情况。我们支持一层显式或隐式
// 在结构的字段上使用标记字符串进行标记。

// fieldParameters是结构字段中标记字符串的解析表示形式。
type fieldParameters struct {
	optional     bool   // true如果该字段是可选的
	explicit     bool   // true如果显式标记正在使用。
	application  bool   // 如果应用程序标记正在使用，则为true。
	private      bool   // 如果使用私有标记，则为true。
	defaultValue *int64 // 整型字段的默认值（可能为零）。
	tag          *int   // 显式或隐式标记（可能为nil）。
	stringType   int    // 封送处理时要使用的字符串标记。
	timeType     int    // 封送时要使用的时间标记。
	set          bool   // true如果在封送处理时为空，则应将其编码为集合
	omitEmpty    bool   // true如果为空，则应忽略。

	// 不变量：
	// 如果设置了explicit，则标记为非零。
}

// 给定一个具有包注释中指定格式的标记字符串，
// parseFieldParameters将其解析为fieldParameters结构，
// 忽略字符串的未知部分。
func parseFieldParameters(str string) (ret fieldParameters) {
	var part string
	for len(str) > 0 {
		part, str, _ = strings.Cut(str, ",")
		switch {
		case part == "optional":
			ret.optional = true
		case part == "explicit":
			ret.explicit = true
			if ret.tag == nil {
				ret.tag = new(int)
			}
		case part == "generalized":
			ret.timeType = TagGeneralizedTime
		case part == "utc":
			ret.timeType = TagUTCTime
		case part == "ia5":
			ret.stringType = TagIA5String
		case part == "printable":
			ret.stringType = TagPrintableString
		case part == "numeric":
			ret.stringType = TagNumericString
		case part == "utf8":
			ret.stringType = TagUTF8String
		case strings.HasPrefix(part, "default:"):
			i, err := strconv.ParseInt(part[8:], 10, 64)
			if err == nil {
				ret.defaultValue = new(int64)
				*ret.defaultValue = i
			}
		case strings.HasPrefix(part, "tag:"):
			i, err := strconv.Atoi(part[4:])
			if err == nil {
				ret.tag = new(int)
				*ret.tag = i
			}
		case part == "set":
			ret.set = true
		case part == "application":
			ret.application = true
			if ret.tag == nil {
				ret.tag = new(int)
			}
		case part == "private":
			ret.private = true
			if ret.tag == nil {
				ret.tag = new(int)
			}
		case part == "omitempty":
			ret.omitEmpty = true
		}
	}
	return
}

// 给定反射的Go类型，getUniversalType返回默认标记号
// 和预期的复合标志。
func getUniversalType(t reflect.Type) (matchAny bool, tagNumber int, isCompound, ok bool) {
	switch t {
	case rawValueType:
		return true, -1, false, true
	case objectIdentifierType:
		return false, TagOID, false, true
	case bitStringType:
		return false, TagBitString, false, true
	case timeType:
		return false, TagUTCTime, false, true
	case enumeratedType:
		return false, TagEnum, false, true
	case bigIntType:
		return false, TagInteger, false, true
	}
	switch t.Kind() {
	case reflect.Bool:
		return false, TagBoolean, false, true
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return false, TagInteger, false, true
	case reflect.Struct:
		return false, TagSequence, true, true
	case reflect.Slice:
		if t.Elem().Kind() == reflect.Uint8 {
			return false, TagOctetString, false, true
		}
		if strings.HasSuffix(t.Name(), "SET") {
			return false, TagSet, true, true
		}
		return false, TagSequence, true, true
	case reflect.String:
		return false, TagPrintableString, false, true
	}
	return false, 0, false, false
}
